package com.jcgyl.export.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Date;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import org.sql2o.Connection;

import com.jcgyl.export.entity.Tbuploadinfo;

//commented code blocks are only used for CORS environments
public class UploadReceiver extends HttpServlet {
	private static final File UPLOAD_DIR = new File("/test/uploads");
	private static File TEMP_DIR = new File("/test/uploadsTemp");

	private static String CONTENT_LENGTH = "Content-Length";
	private static int SUCCESS_RESPONSE_CODE = 200;

	final Logger log = LoggerFactory.getLogger(UploadReceiver.class);

	@Override
	public void init() throws ServletException {
		UPLOAD_DIR.mkdirs();
		SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
	}

	@Override
	public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		String uuid = req.getPathInfo().replaceAll("/", "");

		handleDeleteFileRequest(uuid, resp);
	}

	private void handleDeleteFileRequest(String uuid, HttpServletResponse resp) throws IOException {
		FileUtils.deleteDirectory(new File(UPLOAD_DIR, uuid));

		if (new File(UPLOAD_DIR, uuid).exists()) {
			log.warn("couldn't find or delete " + uuid);
		} else {
			log.info("deleted " + uuid);
		}

		resp.setStatus(SUCCESS_RESPONSE_CODE);
		// resp.addHeader("Access-Control-Allow-Origin", "*");
	}

	@Override
	public void doOptions(HttpServletRequest req, HttpServletResponse resp) {
		resp.setStatus(SUCCESS_RESPONSE_CODE);
		resp.addHeader("Access-Control-Allow-Origin", "*");
		// resp.addHeader("Access-Control-Allow-Credentials", "true");
		resp.addHeader("Access-Control-Allow-Methods", "POST, DELETE");
		resp.addHeader("Access-Control-Allow-Headers", "x-requested-with, cache-control, content-type");
	}

	@Override
	public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		RequestParser requestParser = null;

		boolean isIframe = req.getHeader("X-Requested-With") == null
				|| !req.getHeader("X-Requested-With").equals("XMLHttpRequest");
		Tbuploadinfo upload = null;
		try {
			// resp.setContentType(isIframe ? "text/html" : "text/plain");
			resp.setContentType("text/plain");
			resp.setStatus(SUCCESS_RESPONSE_CODE);

			// resp.addHeader("Access-Control-Allow-Origin", "http://192.168.130.118:8080");
			// resp.addHeader("Access-Control-Allow-Credentials", "true");
			// resp.addHeader("Access-Control-Allow-Origin", "*");

			if (ServletFileUpload.isMultipartContent(req)) {
				MultipartUploadParser multipartUploadParser = new MultipartUploadParser(req, TEMP_DIR,
						getServletContext());
				requestParser = RequestParser.getInstance(req, multipartUploadParser);
				upload = writeFileForMultipartRequest(requestParser);
				writeResponse(resp.getWriter(), requestParser.generateError() ? "Generated error" : null, isIframe,
						false, requestParser, upload);
			} else {
				requestParser = RequestParser.getInstance(req, null);

				// handle POST delete file request
				if (requestParser.getMethod() != null && requestParser.getMethod().equalsIgnoreCase("DELETE")) {
					String uuid = requestParser.getUuid();
					handleDeleteFileRequest(uuid, resp);
				} else {
					upload = writeFileForNonMultipartRequest(req, requestParser);
					writeResponse(resp.getWriter(), requestParser.generateError() ? "Generated error" : null, isIframe,
							false, requestParser, upload);
				}
			}
		} catch (Exception e) {
			log.error("Problem handling upload request", e);
			if (e instanceof MergePartsException) {
				writeResponse(resp.getWriter(), e.getMessage(), isIframe, true, requestParser, upload);
			} else {
				writeResponse(resp.getWriter(), e.getMessage(), isIframe, false, requestParser, upload);
			}
		}
	}

	private Tbuploadinfo writeFileForNonMultipartRequest(HttpServletRequest req, RequestParser requestParser)
			throws Exception {
		File dir = new File(UPLOAD_DIR, requestParser.getUuid());
		dir.mkdirs();
		Tbuploadinfo upload = new Tbuploadinfo();
		String contentLengthHeader = req.getHeader(CONTENT_LENGTH);
		long expectedFileSize = Long.parseLong(contentLengthHeader);
		File outputFile = null;
		if (requestParser.getPartIndex() >= 0) {
			writeFile(req.getInputStream(),
					new File(dir, requestParser.getUuid() + "_" + String.format("%05d", requestParser.getPartIndex())),
					null);

			if (requestParser.getTotalParts() - 1 == requestParser.getPartIndex()) {
				File[] parts = getPartitionFiles(dir, requestParser.getUuid());
				outputFile = new File(dir, requestParser.getFilename());
				for (File part : parts) {
					mergeFiles(outputFile, part);
				}

				assertCombinedFileIsVaid(requestParser.getTotalFileSize(), outputFile, requestParser.getUuid());
				deletePartitionFiles(dir, requestParser.getUuid());
			}
		} else {
			outputFile = new File(dir, requestParser.getFilename());
			writeFile(req.getInputStream(), outputFile, expectedFileSize);
		}
		// 保存到数据库
		String updateSql = "insert into  upload_info(file_path,uploador_name,upload_time) values(:path,:name,:time)";

		try (Connection con = MyDao.getInstance().open()) {
			int insertedId = (int) con.createQuery(updateSql, true).addParameter("path", outputFile.getAbsolutePath())//
					.addParameter("name", "system") //
					.addParameter("time", new Date())//
					.executeUpdate().getKey();
			upload.setUploadId(insertedId);
			// con.createQuery(updateSql,true).bind(upload).executeUpdate();
		} catch (Exception e) {
			log.error(e.getMessage());
			// TODO: handle exception
		}
		return upload;
	}

	private Tbuploadinfo writeFileForMultipartRequest(RequestParser requestParser) throws Exception {
		File dir = new File(UPLOAD_DIR, requestParser.getUuid());
		dir.mkdirs();
		log.error("创建临时目录成功...");
		Tbuploadinfo upload = new Tbuploadinfo();
		File outputFile = null;
		try {
			if (requestParser.getPartIndex() >= 0) {
				writeFile(requestParser.getUploadItem().getInputStream(),
						new File(dir,
								requestParser.getUuid() + "_" + String.format("%05d", requestParser.getPartIndex())),
						null);

				if (requestParser.getTotalParts() - 1 == requestParser.getPartIndex()) {
					File[] parts = getPartitionFiles(dir, requestParser.getUuid());
					outputFile = new File(dir, requestParser.getOriginalFilename());
					for (File part : parts) {
						mergeFiles(outputFile, part);
					}

					assertCombinedFileIsVaid(requestParser.getTotalFileSize(), outputFile, requestParser.getUuid());
					deletePartitionFiles(dir, requestParser.getUuid());
				}
			} else {
				outputFile = new File(dir, requestParser.getFilename());

				// requestParser.getUploadItem().write(outputFile);
				writeFile(requestParser.getUploadItem().getInputStream(), outputFile, null);
			}
		} catch (Exception e) {
			log.error(e.getMessage());
		}
		// 保存到数据库
		String updateSql = "insert into  upload_info(file_path,uploador_name,upload_time) values(:path,:name,:time)";

		try (Connection con = MyDao.getInstance().open()) {
			int insertedId = (int) con.createQuery(updateSql, true).addParameter("path", outputFile.getAbsolutePath())//
					.addParameter("name", "system") //
					.addParameter("time", new Date())//
					.executeUpdate().getKey();
			upload.setUploadId(insertedId);
		} catch (Exception e) {
			log.error(e.getMessage());
		}
		return upload;
	}

	private void assertCombinedFileIsVaid(long totalFileSize, File outputFile, String uuid) throws MergePartsException {
		if (totalFileSize != outputFile.length()) {
			deletePartitionFiles(UPLOAD_DIR, uuid);
			outputFile.delete();
			throw new MergePartsException("Incorrect combined file size!");
		}

	}

	private static class PartitionFilesFilter implements FilenameFilter {
		private String filename;

		PartitionFilesFilter(String filename) {
			this.filename = filename;
		}

		@Override
		public boolean accept(File file, String s) {
			return s.matches(Pattern.quote(filename) + "_\\d+");
		}
	}

	private static File[] getPartitionFiles(File directory, String filename) {
		File[] files = directory.listFiles(new PartitionFilesFilter(filename));
		Arrays.sort(files);
		return files;
	}

	private static void deletePartitionFiles(File directory, String filename) {
		File[] partFiles = getPartitionFiles(directory, filename);
		for (File partFile : partFiles) {
			partFile.delete();
		}
	}

	private File mergeFiles(File outputFile, File partFile) throws IOException {
		FileOutputStream fos = new FileOutputStream(outputFile, true);

		try {
			FileInputStream fis = new FileInputStream(partFile);

			try {
				IOUtils.copy(fis, fos);
			} finally {
				IOUtils.closeQuietly(fis);
			}
		} finally {
			IOUtils.closeQuietly(fos);
		}

		return outputFile;
	}

	private File writeFile(InputStream in, File out, Long expectedFileSize) throws IOException {
		FileOutputStream fos = null;

		try {
			fos = new FileOutputStream(out);

			IOUtils.copy(in, fos);

			if (expectedFileSize != null) {
				Long bytesWrittenToDisk = out.length();
				if (!expectedFileSize.equals(bytesWrittenToDisk)) {
					log.warn("Expected file {} to be {} bytes; file on disk is {} bytes",
							new Object[] { out.getAbsolutePath(), expectedFileSize, 1 });
					out.delete();
					throw new IOException(
							String.format("Unexpected file size mismatch. Actual bytes %s. Expected bytes %s.",
									bytesWrittenToDisk, expectedFileSize));
				}
			}

			return out;
		} catch (Exception e) {
			throw new IOException(e);
		} finally {
			IOUtils.closeQuietly(fos);
		}
	}

	private void writeResponse(PrintWriter writer, String failureReason, boolean isIframe, boolean restartChunking,
			RequestParser requestParser, Tbuploadinfo upload) {
		if (failureReason == null) {
			// if (isIframe)
			// {
			// writer.print("{\"success\": true, \"uuid\": \"" + requestParser.getUuid() +
			// "\"}<script
			// src=\"http://192.168.130.118:8080/client/js/iframe.xss.response.js\"></script>");
			// }
			// else
			// {
			writer.print("{\"uploadId\": " + upload.getUploadId() + "}");
			// }
		} else {
			if (restartChunking) {
				writer.print("{\"error\": \"" + failureReason + "\", \"reset\": true}");
			} else {
				// if (isIframe)
				// {
				// writer.print("{\"error\": \"" + failureReason + "\", \"uuid\": \"" +
				// requestParser.getUuid() + "\"}<script
				// src=\"http://192.168.130.118:8080/client/js/iframe.xss.response.js\"></script>");
				// }
				// else
				// {

				writer.print("{\"error\": \"" + failureReason + "\"}");
				// }
			}
		}
	}

	private class MergePartsException extends Exception {
		MergePartsException(String message) {
			super(message);
		}
	}
}